package com.analyse.stock.utils;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.analyse.stock.constant.StockConstant;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.*;
import java.io.*;
import java.lang.reflect.Type;
import java.net.*;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * 通用http发送方法
 * Created by liang on 2021/03/21.
 */
public class HttpUtil {
    private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
    
    private static final PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();

    static {
        connManager.setMaxTotal(200);
        connManager.setDefaultMaxPerRoute(20);
    }

    /**
     * 获取http连接
     *
     * @return
     */
    public static CloseableHttpClient getHttpClient() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(6000)
                .setSocketTimeout(6000)
                .setConnectionRequestTimeout(6000)
                .build();

        HttpRequestRetryHandler retry = new HttpRequestRetryHandler() {

            @Override
            public boolean retryRequest(IOException exception, int exceptionCount, HttpContext context) {
                if (exceptionCount > 3) {
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    return true;
                }
                if (exception instanceof UnknownHostException) {
                    return false;
                }
                if (exception instanceof ConnectTimeoutException) {
                    return false;
                }
                if (exception instanceof SSLException) {
                    return false;
                }

                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }

                return false;
            }
        };
        return HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .setRetryHandler(retry)
                .setConnectionManager(connManager)
                .build();
    }

    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @param charsetName 编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url,String accept, String charsetName,Long timeout) {
        String line = null;
        BufferedReader in = null;
        try{
            log.debug("sendGet - {}", url);
            URL realUrl = new URL(url);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", accept);
            connection.setRequestProperty("content-type", String.format("%s;charset=%s",accept,charsetName));
            connection.setConnectTimeout(timeout.intValue());
            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), charsetName));
            while ((line = in.readLine()) != null){
                if(StringUtils.isNotBlank(line)){
                    break;
                }
            }
        }
        catch (ConnectException e){
            log.error("调用HttpUtils.sendGet ConnectException, url={}",url, e);
        }catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url={}",url, e);
        }catch (IOException e){
            log.error("调用HttpUtils.sendGet IOException, url={}",url, e);
        }catch (Exception e){
            log.error("调用HttpUtils.sendGet Exception, url={}",url, e);
        }finally{
            try{
                if (in != null){
                    in.close();
                }
            }catch (Exception ex){
                log.error("调用in.close Exception, url={}",url);
            }
        }
        return line;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数，请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param)
    {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try
        {
            String urlNameString = url + "?" + param;
            log.info("sendPost - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (out != null)
                {
                    out.close();
                }
                if (in != null)
                {
                    in.close();
                }
            }
            catch (IOException ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    public static String sendSSLPost(String url, String param)
    {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        try
        {
            log.info("sendSSLPost - {}", urlNameString);
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);

            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret = "";
            while ((ret = br.readLine()) != null)
            {
                if (ret != null && !ret.trim().equals(""))
                {
                    result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
                }
            }
            log.info("recv - {}", result);
            conn.disconnect();
            br.close();
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
        }
        return result.toString();
    }

    private static class TrustAnyTrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
        {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
        {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[] {};
        }
    }

    private static class TrustAnyHostnameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }

    public static <T>T post(String url, Object param, Class<T> clazz){
        String result = post(url, JSON.toJSONString(param));
        return JSON.parseObject(result, clazz);
    }

    /**
     * http post请求
     * @param url
     * @param params
     * @return
     */
    public static String post(String url, String params) {
        return post(url,params,"application/json");
    }
    public static String post(String url, String params,String contentType) {
        return post(url,params,contentType,null);
    }
    /**
     * http post请求
     * @param url
     * @param params
     * @return
     */
    public static String post(String url, String params, String contentType, Map<String,String> headers) {
        long start = System.currentTimeMillis();
        CloseableHttpClient httpClient = getHttpClient();
        HttpPost httpPost = new HttpPost(URI.create(url));
        CloseableHttpResponse httpResponse = null;
        try {
            StringEntity stringEntity = new StringEntity(params, "UTF-8");
            stringEntity.setContentType(contentType);
            httpPost.setEntity(stringEntity);

            if(CollUtil.isNotEmpty(headers) && headers.size() > 0){
                Iterator headerIterator = headers.entrySet().iterator();
                while(headerIterator.hasNext()) {
                    Map.Entry<String,String> elem = (Map.Entry<String,String>) headerIterator.next();
                    httpPost.addHeader(elem.getKey(), elem.getValue());
                }
            }
            //发送post请求
            httpResponse = httpClient.execute(httpPost);
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                HttpEntity httpEntity = httpResponse.getEntity();
                return EntityUtils.toString(httpEntity);
            }
            System.out.println("response error status:" + httpResponse.getStatusLine().getStatusCode());
        } catch (Exception e) {
            log.error("http post请求失败.....", e);
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
            } catch (Exception e2) {
                log.error("释放http连接失败.....", e2);
            }
            log.info(String.format("post请求结束,耗时:%d", System.currentTimeMillis() - start));
        }
        return null;
    }
    public static String post(String url, String params, Map<String, String> headers) {
        return post(url,params,"application/json",headers);
    }

    private static Type buildType(Type... types) {
        ParameterizedTypeImpl beforeType = null;
        if (types != null && types.length > 0) {
            for (int i = types.length - 1; i > 0; i--) {
                beforeType = new ParameterizedTypeImpl(new Type[]{beforeType == null ? types[i] : beforeType}, null, types[i - 1]);
            }
        }
        return beforeType;
    }

    public static CloseableHttpClient createSSLClientDefault() {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                // 信任所有
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            }).build();
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            return HttpClients.custom().setSSLSocketFactory(sslsf).build();
        } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
            e.printStackTrace();
        }
        return HttpClients.createDefault();
    }

    /**
     * 发送https请求
     * @throws Exception
     */
    public static String sendByHttp(String url,Map<String,Object> jsonObject,String accept,String charsetName) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        try {
            RequestConfig rc = RequestConfig.custom().setConnectTimeout(6000).setSocketTimeout(6000).build();
            HttpClientBuilder clientBuilder = HttpClientBuilder.create().setRedirectStrategy(new LaxRedirectStrategy());
            //URL url2 = new URL(url);

            //为自定义连接器注册http与https
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", new PlainConnectionSocketFactory())
                    .register("https", getSSLConnectionSocketFactory()).build();
            PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
            phccm.setMaxTotal(500);

            //if ("https".equalsIgnoreCase(url2.getProtocol())) {
            //clientBuilder.setSSLSocketFactory(getSSLConnectionSocketFactory());
            clientBuilder.setConnectionReuseStrategy(NoConnectionReuseStrategy.INSTANCE);
            clientBuilder.setConnectionManagerShared(true);
            clientBuilder.setConnectionManager(phccm);
            //}

            httpClient = clientBuilder.build();
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-type", String.format("%s;charset=%s",accept,charsetName));
            httpPost.setHeader("Accept", accept);
            UrlEncodedFormEntity formEntity = null;
            if(CollUtil.isNotEmpty(jsonObject)){
                //获取Map集合的键集合
                Set<String> keySet = jsonObject.keySet();
                Iterator iterator = keySet.iterator();
                List<NameValuePair> list = new ArrayList<>();
                while (iterator.hasNext()){
                    String key = (String)iterator.next();
                    Object value = jsonObject.get(key);
                    //BasicNameValuePair通常是用来封装post请求中的参数名称和值
                    BasicNameValuePair basicNameValuePair = new BasicNameValuePair(key,value.toString());
                    list.add(basicNameValuePair);
                }
                //构造器方式注入Entity
                formEntity = new UrlEncodedFormEntity(list);

                //将绑定了参数的HttpEntity子类实现类存入HttpPost对象中
                httpPost.setEntity(formEntity);
            }
            httpPost.setConfig(rc);
            httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if (httpEntity != null) {
                return EntityUtils.toString(httpEntity, "UTF-8");
            } else {
                return "";
            }
        } catch (Exception ex) {
            log.error("### sendByHttp:{}",ex.getMessage(),ex);
            return "";
        } finally {
            try {
                if(httpResponse != null){
                    httpResponse.close();
                }
                if(httpClient != null){
                    httpClient.close();
                }
                log.info("请求流关闭完成");
            } catch (IOException ex) {
                log.error("请求流关闭出错:{}",ex.getMessage(),ex);
            }
        }
    }

    public static SSLConnectionSocketFactory getSSLConnectionSocketFactory() throws KeyManagementException, NoSuchAlgorithmException {
        //SSLContext ctx = SSLContext.getInstance("TLS");
        final SSLContextBuilder builder = new SSLContextBuilder();
        try{
            //全部信任 不做身份鉴定
            builder.loadTrustMaterial(null, (X509Certificate[] x509Certificate, String s) -> true);
        }catch (NoSuchAlgorithmException | KeyStoreException ex){
            log.error("SSLContextBuilder.loadTrustMaterial 异常：{}",ex.getMessage(),ex);
        }
        //ctx.init((KeyManager[])null, new TrustManager[]{tm}, (SecureRandom)null);
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
                builder.build(), new String[]{"TLSv1", "TLSv1.2","SSLv3"}, null, NoopHostnameVerifier.INSTANCE);
        return socketFactory;
    }


    public static void main(String[] args) {
        String url = "https://1.push2.eastmoney.com/api/qt/ulist/sse";
        String accept = "text/event-stream";
        String param = "secids=0.000564,1.601216,0.002241,1.600009,1.603799,1.600171,0.001696,1.600460,0.002583,0.002252,0.002555,0.002007,1.600686,1.601888,1.600845,1.603373,0.002100,0.000088,1.600678,1.603375,1.600089,1.601127,0.002457,1.688008,1.600862,0.300719,0.300068,1.601698,0.002274,0.000608,0.002232,0.002869,0.002371,0.003026,0.300339,1.601360,1.600111,1.600760&fields=f12,f13,f14,f19,f139,f148,f2,f4,f1,f125,f18,f3,f152,f62,f63,f64,f65,f66,f69,f70,f71,f72,f75,f76,f77,f78,f81,f82,f83,f84,f87&invt=3&ut=fa5fd1943c7b386f172d6893dbfba10b&fid=&po=1&pi=0&pz=38&mpi=6000&dect=1";
        String result2 = sendGet(url+"?"+param,accept,StockConstant.UTF8,6000L);
        System.out.println("返回："+result2.replace("data:",""));
    }
}